package com.czm.art_light.aspect;

import com.czm.art_light.annotation.CacheWithLock;
import com.czm.art_light.annotation.DistributedLock;
import com.czm.art_light.constant.ArtErrorCodeEnum;
import com.czm.art_light.exception.BusinessException;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * 缓存AOP
 */
@Aspect
@Component
public class CacheWithLockAspect {

    private static final String NULL_CACHE = "NULL_CACHE";
    private static final String LOCK_PREFIX = "LOCK::";
    private static final String KEY_SEPARATOR = "::";

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    private final ExpressionParser parser = new SpelExpressionParser();
    private final ParserContext spelParserContext = new TemplateParserContext();

    @Around("@annotation(cacheWithLock)")
    @SneakyThrows
    public Object around(ProceedingJoinPoint joinPoint, CacheWithLock cacheWithLock) throws Throwable {
        // 1. 生成缓存Key
        String cacheKey = buildKey(cacheWithLock, joinPoint);

        // 2. 先尝试从缓存获取
        Object cachedValue = redisTemplate.opsForValue().get(cacheKey);
        if (cachedValue != null) {
            // 缓存中有值
            return handleCachedValue(cachedValue);
        }

        // 3. 判断是否需要分布式锁
        if (cacheWithLock.enabled()) {
            // 锁Key=前缀+缓存Key
            String lockKey = LOCK_PREFIX + cacheKey;
            return handleWithLock(joinPoint, cacheWithLock, cacheKey, lockKey);
        }

        // 4. 直接执行并缓存结果
        return handleWithoutLock(joinPoint, cacheKey, cacheWithLock);
    }

    private Object handleWithLock(ProceedingJoinPoint joinPoint,
                                  CacheWithLock config,
                                  String cacheKey,
                                  String lockKey) throws Throwable {
        RLock lock = redissonClient.getLock(lockKey);
        boolean locked = false;

        try {
            // 尝试获取锁（带超时时间）
            locked = lock.tryLock(5, 30, TimeUnit.SECONDS);
            if (!locked) {
                throw new BusinessException(ArtErrorCodeEnum.GET_LOCK_ERROR);
            }

            // 二次检查缓存
            Object cachedValue = redisTemplate.opsForValue().get(cacheKey);
            if (cachedValue != null) {
                return handleCachedValue(cachedValue);
            }

            // 执行目标方法
            Object result = joinPoint.proceed();

            // 处理缓存
            processCaching(cacheKey, result, config);

            return result;
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    private Object handleWithoutLock(ProceedingJoinPoint joinPoint,
                                     String cacheKey,
                                     CacheWithLock config) throws Throwable {
        // 直接执行并缓存结果
        Object result = joinPoint.proceed();
        processCaching(cacheKey, result, config);
        return result;
    }

    /**
     * 统一键生成策略
     */
    private String buildKey(CacheWithLock config, ProceedingJoinPoint joinPoint) {
        if (config.key() == null || config.key().isEmpty()) {
            return config.cacheName();
        }
        // 解析SpEL表达式
        Expression expression = parser.parseExpression(config.key());

        // 构建评估上下文
        EvaluationContext context = new MethodBasedEvaluationContext(
                joinPoint.getThis(),
                ((MethodSignature) joinPoint.getSignature()).getMethod(),
                joinPoint.getArgs(),
                new DefaultParameterNameDiscoverer()
        );

        // 生成基础键值
        String keyValue = expression.getValue(context, String.class);

        // 组合完整缓存Key
        return config.cacheName() + KEY_SEPARATOR + keyValue;
    }

    /**
     * 处理缓存
     */
    private void processCaching(String cacheKey, Object result, CacheWithLock config) {
        if (result == null) {
            if (config.cacheNull()) {
                redisTemplate.opsForValue().set(
                        cacheKey,
                        NULL_CACHE,
                        config.nullExpireTime(),
                        TimeUnit.SECONDS
                );
            }
        } else {
            redisTemplate.opsForValue().set(
                    cacheKey,
                    result,
                    config.expireTime(),
                    TimeUnit.SECONDS
            );
        }
    }

    private Object handleCachedValue(Object cachedValue) {
        return NULL_CACHE.equals(cachedValue) ? null : cachedValue;
    }
}